"""Tests for unix_events.py."""

import gc
import errno
import io
import pprint
import signal
import stat
import sys
import tempfile
import unittest
import unittest.mock


from tulip import events
from tulip import futures
from tulip import protocols
from tulip import test_utils
from tulip import unix_events


@unittest.skipUnless(signal, 'Signals are not supported')
class SelectorEventLoopTests(unittest.TestCase):

    def setUp(self):
        self.loop = unix_events.SelectorEventLoop()
        events.set_event_loop(None)

    def tearDown(self):
        self.loop.close()

    def test_check_signal(self):
        self.assertRaises(
            TypeError, self.loop._check_signal, '1')
        self.assertRaises(
            ValueError, self.loop._check_signal, signal.NSIG + 1)

    def test_handle_signal_no_handler(self):
        self.loop._handle_signal(signal.NSIG + 1, ())

    def test_handle_signal_cancelled_handler(self):
        h = events.Handle(unittest.mock.Mock(), ())
        h.cancel()
        self.loop._signal_handlers[signal.NSIG + 1] = h
        self.loop.remove_signal_handler = unittest.mock.Mock()
        self.loop._handle_signal(signal.NSIG + 1, ())
        self.loop.remove_signal_handler.assert_called_with(signal.NSIG + 1)

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_add_signal_handler_setup_error(self, m_signal):
        m_signal.NSIG = signal.NSIG
        m_signal.set_wakeup_fd.side_effect = ValueError

        self.assertRaises(
            RuntimeError,
            self.loop.add_signal_handler,
            signal.SIGINT, lambda: True)

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_add_signal_handler(self, m_signal):
        m_signal.NSIG = signal.NSIG

        cb = lambda: True
        self.loop.add_signal_handler(signal.SIGHUP, cb)
        h = self.loop._signal_handlers.get(signal.SIGHUP)
        self.assertTrue(isinstance(h, events.Handle))
        self.assertEqual(h._callback, cb)

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_add_signal_handler_install_error(self, m_signal):
        m_signal.NSIG = signal.NSIG

        def set_wakeup_fd(fd):
            if fd == -1:
                raise ValueError()
        m_signal.set_wakeup_fd = set_wakeup_fd

        class Err(OSError):
            errno = errno.EFAULT
        m_signal.signal.side_effect = Err

        self.assertRaises(
            Err,
            self.loop.add_signal_handler,
            signal.SIGINT, lambda: True)

    @unittest.mock.patch('tulip.unix_events.signal')
    @unittest.mock.patch('tulip.unix_events.tulip_log')
    def test_add_signal_handler_install_error2(self, m_logging, m_signal):
        m_signal.NSIG = signal.NSIG

        class Err(OSError):
            errno = errno.EINVAL
        m_signal.signal.side_effect = Err

        self.loop._signal_handlers[signal.SIGHUP] = lambda: True
        self.assertRaises(
            RuntimeError,
            self.loop.add_signal_handler,
            signal.SIGINT, lambda: True)
        self.assertFalse(m_logging.info.called)
        self.assertEqual(1, m_signal.set_wakeup_fd.call_count)

    @unittest.mock.patch('tulip.unix_events.signal')
    @unittest.mock.patch('tulip.unix_events.tulip_log')
    def test_add_signal_handler_install_error3(self, m_logging, m_signal):
        class Err(OSError):
            errno = errno.EINVAL
        m_signal.signal.side_effect = Err
        m_signal.NSIG = signal.NSIG

        self.assertRaises(
            RuntimeError,
            self.loop.add_signal_handler,
            signal.SIGINT, lambda: True)
        self.assertFalse(m_logging.info.called)
        self.assertEqual(2, m_signal.set_wakeup_fd.call_count)

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_remove_signal_handler(self, m_signal):
        m_signal.NSIG = signal.NSIG

        self.loop.add_signal_handler(signal.SIGHUP, lambda: True)

        self.assertTrue(
            self.loop.remove_signal_handler(signal.SIGHUP))
        self.assertTrue(m_signal.set_wakeup_fd.called)
        self.assertTrue(m_signal.signal.called)
        self.assertEqual(
            (signal.SIGHUP, m_signal.SIG_DFL), m_signal.signal.call_args[0])

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_remove_signal_handler_2(self, m_signal):
        m_signal.NSIG = signal.NSIG
        m_signal.SIGINT = signal.SIGINT

        self.loop.add_signal_handler(signal.SIGINT, lambda: True)
        self.loop._signal_handlers[signal.SIGHUP] = object()
        m_signal.set_wakeup_fd.reset_mock()

        self.assertTrue(
            self.loop.remove_signal_handler(signal.SIGINT))
        self.assertFalse(m_signal.set_wakeup_fd.called)
        self.assertTrue(m_signal.signal.called)
        self.assertEqual(
            (signal.SIGINT, m_signal.default_int_handler),
            m_signal.signal.call_args[0])

    @unittest.mock.patch('tulip.unix_events.signal')
    @unittest.mock.patch('tulip.unix_events.tulip_log')
    def test_remove_signal_handler_cleanup_error(self, m_logging, m_signal):
        m_signal.NSIG = signal.NSIG
        self.loop.add_signal_handler(signal.SIGHUP, lambda: True)

        m_signal.set_wakeup_fd.side_effect = ValueError

        self.loop.remove_signal_handler(signal.SIGHUP)
        self.assertTrue(m_logging.info)

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_remove_signal_handler_error(self, m_signal):
        m_signal.NSIG = signal.NSIG
        self.loop.add_signal_handler(signal.SIGHUP, lambda: True)

        m_signal.signal.side_effect = OSError

        self.assertRaises(
            OSError, self.loop.remove_signal_handler, signal.SIGHUP)

    @unittest.mock.patch('tulip.unix_events.signal')
    def test_remove_signal_handler_error2(self, m_signal):
        m_signal.NSIG = signal.NSIG
        self.loop.add_signal_handler(signal.SIGHUP, lambda: True)

        class Err(OSError):
            errno = errno.EINVAL
        m_signal.signal.side_effect = Err

        self.assertRaises(
            RuntimeError, self.loop.remove_signal_handler, signal.SIGHUP)

    @unittest.mock.patch('os.WTERMSIG')
    @unittest.mock.patch('os.WEXITSTATUS')
    @unittest.mock.patch('os.WIFSIGNALED')
    @unittest.mock.patch('os.WIFEXITED')
    @unittest.mock.patch('os.waitpid')
    def test__sig_chld(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED,
                       m_WEXITSTATUS, m_WTERMSIG):
        m_waitpid.side_effect = [(7, object()), ChildProcessError]
        m_WIFEXITED.return_value = True
        m_WIFSIGNALED.return_value = False
        m_WEXITSTATUS.return_value = 3
        transp = unittest.mock.Mock()
        self.loop._subprocesses[7] = transp

        self.loop._sig_chld()
        transp._process_exited.assert_called_with(3)
        self.assertFalse(m_WTERMSIG.called)

    @unittest.mock.patch('os.WTERMSIG')
    @unittest.mock.patch('os.WEXITSTATUS')
    @unittest.mock.patch('os.WIFSIGNALED')
    @unittest.mock.patch('os.WIFEXITED')
    @unittest.mock.patch('os.waitpid')
    def test__sig_chld_signal(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED,
                              m_WEXITSTATUS, m_WTERMSIG):
        m_waitpid.side_effect = [(7, object()), ChildProcessError]
        m_WIFEXITED.return_value = False
        m_WIFSIGNALED.return_value = True
        m_WTERMSIG.return_value = 1
        transp = unittest.mock.Mock()
        self.loop._subprocesses[7] = transp

        self.loop._sig_chld()
        transp._process_exited.assert_called_with(-1)
        self.assertFalse(m_WEXITSTATUS.called)

    @unittest.mock.patch('os.WTERMSIG')
    @unittest.mock.patch('os.WEXITSTATUS')
    @unittest.mock.patch('os.WIFSIGNALED')
    @unittest.mock.patch('os.WIFEXITED')
    @unittest.mock.patch('os.waitpid')
    def test__sig_chld_zero_pid(self, m_waitpid, m_WIFEXITED, m_WIFSIGNALED,
                                m_WEXITSTATUS, m_WTERMSIG):
        m_waitpid.side_effect = [(0, object()), ChildProcessError]
        transp = unittest.mock.Mock()
        self.loop._subprocesses[7] = transp

        self.loop._sig_chld()
        self.assertFalse(transp._process_exited.called)
        self.assertFalse(m_WIFSIGNALED.called)
        self.assertFalse(m_WIFEXITED.called)
        self.assertFalse(m_WTERMSIG.called)
        self.assertFalse(m_WEXITSTATUS.called)

    @unittest.mock.patch('os.WTERMSIG')
    @unittest.mock.patch('os.WEXITSTATUS')
    @unittest.mock.patch('os.WIFSIGNALED')
    @unittest.mock.patch('os.WIFEXITED')
    @unittest.mock.patch('os.waitpid')
    def test__sig_chld_not_registered_subprocess(self, m_waitpid,
                                                 m_WIFEXITED, m_WIFSIGNALED,
                                                 m_WEXITSTATUS, m_WTERMSIG):
        m_waitpid.side_effect = [(7, object()), ChildProcessError]
        m_WIFEXITED.return_value = True
        m_WIFSIGNALED.return_value = False
        m_WEXITSTATUS.return_value = 3

        self.loop._sig_chld()
        self.assertFalse(m_WTERMSIG.called)

    @unittest.mock.patch('os.WTERMSIG')
    @unittest.mock.patch('os.WEXITSTATUS')
    @unittest.mock.patch('os.WIFSIGNALED')
    @unittest.mock.patch('os.WIFEXITED')
    @unittest.mock.patch('os.waitpid')
    def test__sig_chld_unknown_status(self, m_waitpid,
                                      m_WIFEXITED, m_WIFSIGNALED,
                                      m_WEXITSTATUS, m_WTERMSIG):
        m_waitpid.side_effect = [(7, object()), ChildProcessError]
        m_WIFEXITED.return_value = False
        m_WIFSIGNALED.return_value = False
        transp = unittest.mock.Mock()
        self.loop._subprocesses[7] = transp

        self.loop._sig_chld()
        self.assertFalse(transp._process_exited.called)
        self.assertFalse(m_WEXITSTATUS.called)
        self.assertFalse(m_WTERMSIG.called)

    @unittest.mock.patch('tulip.unix_events.tulip_log')
    @unittest.mock.patch('os.WTERMSIG')
    @unittest.mock.patch('os.WEXITSTATUS')
    @unittest.mock.patch('os.WIFSIGNALED')
    @unittest.mock.patch('os.WIFEXITED')
    @unittest.mock.patch('os.waitpid')
    def test__sig_chld_unknown_status_in_handler(self, m_waitpid,
                                                 m_WIFEXITED, m_WIFSIGNALED,
                                                 m_WEXITSTATUS, m_WTERMSIG,
                                                 m_log):
        m_waitpid.side_effect = Exception
        transp = unittest.mock.Mock()
        self.loop._subprocesses[7] = transp

        self.loop._sig_chld()
        self.assertFalse(transp._process_exited.called)
        self.assertFalse(m_WIFSIGNALED.called)
        self.assertFalse(m_WIFEXITED.called)
        self.assertFalse(m_WTERMSIG.called)
        self.assertFalse(m_WEXITSTATUS.called)
        m_log.exception.assert_called_with(
            'Unknown exception in SIGCHLD handler')


class UnixReadPipeTransportTests(unittest.TestCase):

    def setUp(self):
        self.loop = test_utils.TestLoop()
        self.protocol = test_utils.make_test_protocol(protocols.Protocol)
        self.pipe = unittest.mock.Mock(spec_set=io.RawIOBase)
        self.pipe.fileno.return_value = 5

        fcntl_patcher = unittest.mock.patch('fcntl.fcntl')
        fcntl_patcher.start()
        self.addCleanup(fcntl_patcher.stop)

    def test_ctor(self):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)
        self.loop.assert_reader(5, tr._read_ready)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_made.assert_called_with(tr)

    def test_ctor_with_waiter(self):
        fut = futures.Future(loop=self.loop)
        unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol, fut)
        test_utils.run_briefly(self.loop)
        self.assertIsNone(fut.result())

    @unittest.mock.patch('os.read')
    def test__read_ready(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)
        m_read.return_value = b'data'
        tr._read_ready()

        m_read.assert_called_with(5, tr.max_size)
        self.protocol.data_received.assert_called_with(b'data')

    @unittest.mock.patch('os.read')
    def test__read_ready_eof(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)
        m_read.return_value = b''
        tr._read_ready()

        m_read.assert_called_with(5, tr.max_size)
        self.assertFalse(self.loop.readers)
        test_utils.run_briefly(self.loop)
        self.protocol.eof_received.assert_called_with()
        self.protocol.connection_lost.assert_called_with(None)

    @unittest.mock.patch('os.read')
    def test__read_ready_blocked(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)
        m_read.side_effect = BlockingIOError
        tr._read_ready()

        m_read.assert_called_with(5, tr.max_size)
        test_utils.run_briefly(self.loop)
        self.assertFalse(self.protocol.data_received.called)

    @unittest.mock.patch('tulip.log.tulip_log.exception')
    @unittest.mock.patch('os.read')
    def test__read_ready_error(self, m_read, m_logexc):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)
        err = OSError()
        m_read.side_effect = err
        tr._close = unittest.mock.Mock()
        tr._read_ready()

        m_read.assert_called_with(5, tr.max_size)
        tr._close.assert_called_with(err)
        m_logexc.assert_called_with('Fatal error for %s', tr)

    @unittest.mock.patch('os.read')
    def test_pause(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        m = unittest.mock.Mock()
        self.loop.add_reader(5, m)
        tr.pause()
        self.assertFalse(self.loop.readers)

    @unittest.mock.patch('os.read')
    def test_resume(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        tr.resume()
        self.loop.assert_reader(5, tr._read_ready)

    @unittest.mock.patch('os.read')
    def test_close(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        tr._close = unittest.mock.Mock()
        tr.close()
        tr._close.assert_called_with(None)

    @unittest.mock.patch('os.read')
    def test_close_already_closing(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        tr._closing = True
        tr._close = unittest.mock.Mock()
        tr.close()
        self.assertFalse(tr._close.called)

    @unittest.mock.patch('os.read')
    def test__close(self, m_read):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        err = object()
        tr._close(err)
        self.assertTrue(tr._closing)
        self.assertFalse(self.loop.readers)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_lost.assert_called_with(err)

    def test__call_connection_lost(self):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        err = None
        tr._call_connection_lost(err)
        self.protocol.connection_lost.assert_called_with(err)
        self.pipe.close.assert_called_with()

        self.assertIsNone(tr._protocol)
        self.assertEqual(2, sys.getrefcount(self.protocol),
                         pprint.pformat(gc.get_referrers(self.protocol)))
        self.assertIsNone(tr._loop)
        self.assertEqual(2, sys.getrefcount(self.loop),
                         pprint.pformat(gc.get_referrers(self.loop)))

    def test__call_connection_lost_with_err(self):
        tr = unix_events._UnixReadPipeTransport(
            self.loop, self.pipe, self.protocol)

        err = OSError()
        tr._call_connection_lost(err)
        self.protocol.connection_lost.assert_called_with(err)
        self.pipe.close.assert_called_with()

        self.assertIsNone(tr._protocol)
        self.assertEqual(2, sys.getrefcount(self.protocol),
                         pprint.pformat(gc.get_referrers(self.protocol)))
        self.assertIsNone(tr._loop)
        self.assertEqual(2, sys.getrefcount(self.loop),
                         pprint.pformat(gc.get_referrers(self.loop)))


class UnixWritePipeTransportTests(unittest.TestCase):

    def setUp(self):
        self.loop = test_utils.TestLoop()
        self.protocol = test_utils.make_test_protocol(protocols.BaseProtocol)
        self.pipe = unittest.mock.Mock(spec_set=io.RawIOBase)
        self.pipe.fileno.return_value = 5

        fcntl_patcher = unittest.mock.patch('fcntl.fcntl')
        fcntl_patcher.start()
        self.addCleanup(fcntl_patcher.stop)

        fstat_patcher = unittest.mock.patch('os.fstat')
        m_fstat = fstat_patcher.start()
        st = unittest.mock.Mock()
        st.st_mode = stat.S_IFIFO
        m_fstat.return_value = st
        self.addCleanup(fstat_patcher.stop)

    def test_ctor(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        self.loop.assert_reader(5, tr._read_ready)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_made.assert_called_with(tr)

    def test_ctor_with_waiter(self):
        fut = futures.Future(loop=self.loop)
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol, fut)
        self.loop.assert_reader(5, tr._read_ready)
        test_utils.run_briefly(self.loop)
        self.assertEqual(None, fut.result())

    def test_can_write_eof(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        self.assertTrue(tr.can_write_eof())

    @unittest.mock.patch('os.write')
    def test_write(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        m_write.return_value = 4
        tr.write(b'data')
        m_write.assert_called_with(5, b'data')
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)

    @unittest.mock.patch('os.write')
    def test_write_no_data(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        tr.write(b'')
        self.assertFalse(m_write.called)
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)

    @unittest.mock.patch('os.write')
    def test_write_partial(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        m_write.return_value = 2
        tr.write(b'data')
        m_write.assert_called_with(5, b'data')
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'ta'], tr._buffer)

    @unittest.mock.patch('os.write')
    def test_write_buffer(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'previous']
        tr.write(b'data')
        self.assertFalse(m_write.called)
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'previous', b'data'], tr._buffer)

    @unittest.mock.patch('os.write')
    def test_write_again(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        m_write.side_effect = BlockingIOError()
        tr.write(b'data')
        m_write.assert_called_with(5, b'data')
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'data'], tr._buffer)

    @unittest.mock.patch('tulip.unix_events.tulip_log')
    @unittest.mock.patch('os.write')
    def test_write_err(self, m_write, m_log):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        err = OSError()
        m_write.side_effect = err
        tr._fatal_error = unittest.mock.Mock()
        tr.write(b'data')
        m_write.assert_called_with(5, b'data')
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)
        tr._fatal_error.assert_called_with(err)
        self.assertEqual(1, tr._conn_lost)

        tr.write(b'data')
        self.assertEqual(2, tr._conn_lost)
        tr.write(b'data')
        tr.write(b'data')
        tr.write(b'data')
        tr.write(b'data')
        m_log.warning.assert_called_with(
            'os.write(pipe, data) raised exception.')

    def test__read_ready(self):
        tr = unix_events._UnixWritePipeTransport(self.loop, self.pipe,
                                                 self.protocol)
        tr._read_ready()
        self.assertFalse(self.loop.readers)
        self.assertFalse(self.loop.writers)
        self.assertTrue(tr._closing)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_lost.assert_called_with(None)

    @unittest.mock.patch('os.write')
    def test__write_ready(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        m_write.return_value = 4
        tr._write_ready()
        m_write.assert_called_with(5, b'data')
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)

    @unittest.mock.patch('os.write')
    def test__write_ready_partial(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        m_write.return_value = 3
        tr._write_ready()
        m_write.assert_called_with(5, b'data')
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'a'], tr._buffer)

    @unittest.mock.patch('os.write')
    def test__write_ready_again(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        m_write.side_effect = BlockingIOError()
        tr._write_ready()
        m_write.assert_called_with(5, b'data')
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'data'], tr._buffer)

    @unittest.mock.patch('os.write')
    def test__write_ready_empty(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        m_write.return_value = 0
        tr._write_ready()
        m_write.assert_called_with(5, b'data')
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'data'], tr._buffer)

    @unittest.mock.patch('tulip.log.tulip_log.exception')
    @unittest.mock.patch('os.write')
    def test__write_ready_err(self, m_write, m_logexc):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        m_write.side_effect = err = OSError()
        tr._write_ready()
        m_write.assert_called_with(5, b'data')
        self.assertFalse(self.loop.writers)
        self.assertFalse(self.loop.readers)
        self.assertEqual([], tr._buffer)
        self.assertTrue(tr._closing)
        m_logexc.assert_called_with('Fatal error for %s', tr)
        self.assertEqual(1, tr._conn_lost)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_lost.assert_called_with(err)


    @unittest.mock.patch('os.write')
    def test__write_ready_closing(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        tr._closing = True
        tr._buffer = [b'da', b'ta']
        m_write.return_value = 4
        tr._write_ready()
        m_write.assert_called_with(5, b'data')
        self.assertFalse(self.loop.writers)
        self.assertFalse(self.loop.readers)
        self.assertEqual([], tr._buffer)
        self.protocol.connection_lost.assert_called_with(None)
        self.pipe.close.assert_called_with()

    @unittest.mock.patch('os.write')
    def test_abort(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        self.loop.add_writer(5, tr._write_ready)
        self.loop.add_reader(5, tr._read_ready)
        tr._buffer = [b'da', b'ta']
        tr.abort()
        self.assertFalse(m_write.called)
        self.assertFalse(self.loop.readers)
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)
        self.assertTrue(tr._closing)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_lost.assert_called_with(None)

    def test__call_connection_lost(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        err = None
        tr._call_connection_lost(err)
        self.protocol.connection_lost.assert_called_with(err)
        self.pipe.close.assert_called_with()

        self.assertIsNone(tr._protocol)
        self.assertEqual(2, sys.getrefcount(self.protocol),
                         pprint.pformat(gc.get_referrers(self.protocol)))
        self.assertIsNone(tr._loop)
        self.assertEqual(2, sys.getrefcount(self.loop),
                         pprint.pformat(gc.get_referrers(self.loop)))

    def test__call_connection_lost_with_err(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        err = OSError()
        tr._call_connection_lost(err)
        self.protocol.connection_lost.assert_called_with(err)
        self.pipe.close.assert_called_with()

        self.assertIsNone(tr._protocol)
        self.assertEqual(2, sys.getrefcount(self.protocol),
                         pprint.pformat(gc.get_referrers(self.protocol)))
        self.assertIsNone(tr._loop)
        self.assertEqual(2, sys.getrefcount(self.loop),
                         pprint.pformat(gc.get_referrers(self.loop)))

    def test_close(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        tr.write_eof = unittest.mock.Mock()
        tr.close()
        tr.write_eof.assert_called_with()

    def test_close_closing(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        tr.write_eof = unittest.mock.Mock()
        tr._closing = True
        tr.close()
        self.assertFalse(tr.write_eof.called)

    def test_write_eof(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)

        tr.write_eof()
        self.assertTrue(tr._closing)
        self.assertFalse(self.loop.readers)
        test_utils.run_briefly(self.loop)
        self.protocol.connection_lost.assert_called_with(None)

    def test_write_eof_pending(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        tr._buffer = [b'data']
        tr.write_eof()
        self.assertTrue(tr._closing)
        self.assertFalse(self.protocol.connection_lost.called)

    def test_pause_resume_writing(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        tr.pause_writing()
        self.assertFalse(tr._writing)
        tr.resume_writing()
        self.assertTrue(tr._writing)

    def test_double_pause_resume_writing(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        tr.pause_writing()
        self.assertFalse(tr._writing)
        tr.pause_writing()
        self.assertFalse(tr._writing)
        tr.resume_writing()
        self.assertTrue(tr._writing)
        tr.resume_writing()
        self.assertTrue(tr._writing)

    def test_pause_resume_writing_with_nonempty_buffer(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        tr.pause_writing()
        self.assertFalse(tr._writing)
        self.assertFalse(self.loop.writers)
        self.assertEqual([b'da', b'ta'], tr._buffer)

        tr.resume_writing()
        self.assertTrue(tr._writing)
        self.loop.assert_writer(5, tr._write_ready)
        self.assertEqual([b'da', b'ta'], tr._buffer)

    @unittest.mock.patch('os.write')
    def test__write_ready_on_pause(self, m_write):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        self.loop.add_writer(5, tr._write_ready)
        tr._buffer = [b'da', b'ta']
        tr.pause_writing()

        tr._write_ready()
        self.assertFalse(m_write.called)
        self.assertFalse(self.loop.writers)
        self.assertEqual([b'da', b'ta'], tr._buffer)
        self.assertFalse(tr._writing)

    def test_discard_output(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        tr._buffer = [b'da', b'ta']
        self.loop.add_writer(5, tr._write_ready)
        tr.discard_output()
        self.assertTrue(tr._writing)
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)

    def test_discard_output_without_pending_writes(self):
        tr = unix_events._UnixWritePipeTransport(
            self.loop, self.pipe, self.protocol)
        tr.discard_output()
        self.assertTrue(tr._writing)
        self.assertFalse(self.loop.writers)
        self.assertEqual([], tr._buffer)
